
local strbyte, strmatch, strchar, strsub
    = string.byte, string.match, string.char, string.sub
local ipairs = ipairs
local floor = math.floor
local require = require
local setmetatable, unpack
    = setmetatable, unpack
local palette = require("barf.nes.palette")
local wx = require("wx")


module "barf.nes"


-- Helper function for reading an unsigned 8-bit integer from a file
local function read_uint8(file)
    return strbyte(file:read(1));
end
local function write_uint8(file,i)
    file:write(strchar(i));
end

-- Bank meta
local bank_metatable = {
    __index = {
        bytestring = function(self,bank,bankindex,pos,newvalue)
            if newvalue then
                bank = self[bank];
                local old = bank[bankindex];
                local afterpos = pos+1+#newvalue;
                local new = strsub(old,1,pos) .. newvalue .. strsub(old,afterpos);
                bank[bankindex] = new;
                return afterpos-1;
            else
                bank = self[bank][bankindex];
                return strsub(bank,pos);
            end
        end;
        uint8 = function(self,bank,bankindex,pos,newvalue)
            if newvalue then
                return self:bytestring(bank,bankindex,pos,strchar(newvalue));
            else
                return strbyte(self[bank][bankindex], pos+1);
            end
        end;
        uint16_le = function(self,bank,bankindex,pos,newvalue)
            if newvalue then
                newvalue = strchar(newvalue % 0x100, floor(newvalue / 0x100) % 0x100);
                return self:bytestring(bank,bankindex,pos,newvalue);
            else
                local b1,b2 = strbyte(self[bank][bankindex], pos+1,pos+2);
                return b1 + (b2 * 0x100);
            end
        end;
        string = function(self,bank,bankindex,pos,newvalue)
            if newvalue then
                return self:bytestring(bank,bankindex,pos,self.romlib.encodestring(newvalue) .. '\005');
            else
                return self.romlib.decodestring(strmatch(self[bank][bankindex],"[^\005]*",pos+1));
            end
        end;
    }
}

-- Bank size constants
local PRG_LENGTH = 0x4000
local CHR_LENGTH = 0x2000

function readrom(file)
    -- First four bytes identify this as a NES ROM image
    if file:read(4) ~= 'NES\26' then
        return nil, 'not a recognised NES ROM image';
    end
    -- Get the number of PRG and CHR banks
    local prg_count = read_uint8(file);
    local chr_count = read_uint8(file);
    -- Ignore the rest of the header
    local header = file:read(10);
    -- Read the PRG banks
    local prg_banks = {};
    for i = 1,prg_count do
        local prg = file:read(PRG_LENGTH);
        if (#prg ~= PRG_LENGTH) then  return nil, 'unexpected end of data';  end
        prg_banks[#prg_banks+1] = prg;
    end
    -- Read the CHR banks
    local chr_banks = {};
    for i = 1,chr_count do
        local chr = file:read(CHR_LENGTH);
        if (#chr ~= CHR_LENGTH) then  return nil, 'unexpected end of data';  end
        chr_banks[#chr_banks+1] = chr;
    end
    -- Read the optional title
    local title = file:read(128);
    if (title) then  title = title:match('^[^%z]+');  end
    -- ROM Lib
    local romlib = require("barf.rcr");
    -- Return the extracted details
    return setmetatable({
        ['prg'] = prg_banks, ['chr'] = chr_banks, ['title'] = title, ["header"] = header;
        ['romlib'] = romlib;
    }, bank_metatable);
end

function saverom(file,rom)
    file:write('NES\026')
    write_uint8(file,#rom.prg);
    write_uint8(file,#rom.chr);
    file:write(rom.header);
    for _,bank in ipairs(rom.prg) do
        file:write(bank)
    end
    for _,bank in ipairs(rom.chr) do
        file:write(bank)
    end
    if rom.title then
        rom.title = rom.title:sub(1,128)
        file:write(rom.title)
        file:write( ('\0'):rep(128 - #rom.title) )
    end
end

colours = {};
pens = {};
local pens = pens;
local colours = colours;
for i = 1,256 do
    local rgb = palette[i];
    local col = wx.wxColour(unpack(rgb));
    colours[i] = col;
    pens[i] = wx.wxPen(col, 1, wx.wxSOLID);
end
